﻿// Copyright (c) 2009 Kjetil Eraker Samuelsen
// This source is subject to the Microsoft Public License.
// See http://www.microsoft.com/opensource/licenses.mspx#Ms-PL.
// All other rights reserved.

using System;
using System.Text;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;

namespace Ksamuelsen.Office.Outlook.Addin.VDialer.Interop {
    internal static class CredentialsInterop {
        [DllImport("Advapi32.dll", EntryPoint = "CredReadW", CharSet = CharSet.Unicode, SetLastError = true)]
        private static extern bool CredRead(string target, CRED_TYPE type, int reservedFlag, out IntPtr credentialPtr);

        [DllImport("Advapi32.dll", EntryPoint = "CredWriteW", CharSet = CharSet.Unicode, SetLastError = true)]
        private static extern bool CredWrite([In] ref CREDENTIAL userCredential, [In] UInt32 flags);

        [DllImport("Advapi32.dll", EntryPoint = "CredFree", SetLastError = true)]
        private static extern bool CredFree([In] IntPtr cred);

        public static void StoreCredentials(string key, string username, string password) {
            CREDENTIAL cred = CREDENTIAL.CreateCredential(key, username, password, CRED_TYPE.CRED_TYPE_GENERIC, CRED_PERSIST.CRED_PERSIST_LOCAL_MACHINE);

            if (!CredWrite(ref cred, (UInt32)0))
                Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
        }

        public static void ReadCredentials(string key, out string username, out string password) {
            IntPtr nCredPtr;

            username = String.Empty;
            password = String.Empty;

            bool read = CredentialsInterop.CredRead(key, CRED_TYPE.CRED_TYPE_GENERIC, 0, out nCredPtr);

            if (read) {
                using (CriticalCredentialHandle critCred = new CriticalCredentialHandle(nCredPtr)) {
                    CREDENTIAL cred = critCred.GetCredential();

                    username = Marshal.PtrToStringUni(cred.UserName);
                    password = Marshal.PtrToStringUni(cred.CredentialBlob, (int)cred.CredentialBlobSize / 2);
                }
            } else {
                Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
            }
        }


        internal sealed class CriticalCredentialHandle : CriticalHandleZeroOrMinusOneIsInvalid {
            internal CriticalCredentialHandle(IntPtr handle) {
                SetHandle(handle);
            }

            internal CREDENTIAL GetCredential() {
                if (!IsInvalid) {
                    return (CREDENTIAL)Marshal.PtrToStructure(handle, typeof(CREDENTIAL));
                } else {
                    throw new InvalidOperationException("Invalid CriticalHandle!");
                }
            }


            override protected bool ReleaseHandle() {
                if (!IsInvalid) {
                    CredentialsInterop.CredFree(handle);
                    SetHandleAsInvalid();
                    return true;
                }
                return false;
            }
        }


    }
}
